package name.huzhenbo.java.algorithm.talent.twentyfour;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * This solutions still has many duplicates, and it's bad to printout solution like: -(((1.0/5.0)-5.0)*5.0).
 * <p/>
 * Will improve it sometime.
 */
class TwentyFour {
    private static String[] OPERATORS = new String[]{"+", "-", "*", "/"};

    public static void printOutAllSolutions(Double[] numbers, int countOfOperands, double expectedResult) {
        int count = 0;
        for (String solution : getAllSolutions(numbers, countOfOperands, expectedResult)) {
            System.out.println(solution);
            count++;
        }
        System.out.println("The count of solutions is: " + count);
    }

    public static Set<String> getAllSolutions(Double[] numbers, int countOfOperands, Double expectedResult) {
        Set<String> results = new HashSet<String>();
        for (List<Double> operands : TwentyFourUtils.repeatableCombine(numbers, countOfOperands)) {
            results.addAll(getPermedSolutions(operands.toArray(new Double[0]), expectedResult));
        }
        return results;
    }

    public static Set<String> getPermedSolutions(Double[] operands, Double expectedResult) {
        Set<String> results = new HashSet<String>();
        int countOfOperands = operands.length;
        Double result;
        for (List<Double> permedOperands : TwentyFourUtils.permutate(operands)) {
            TwentyFourOperand twentyFourOperand = new TwentyFourOperand(permedOperands);
            for (List<String> operators : TwentyFourUtils.repeatableCombine(OPERATORS, countOfOperands - 1)) {
                for (List<String> permedOperators : TwentyFourUtils.permutate(operators.toArray(new String[0]))) {
                    result = 0.00;
                    StringBuilder output = new StringBuilder();
                    for (int i = 0; i < countOfOperands; i++) {
                        Double oper = twentyFourOperand.getOperands().get(i);
                        result = (i == 0 ? oper : calculate(result, oper, permedOperators.get(i - 1)));
                        appendToOutput(permedOperators, oper, i, output);
                    }

                    if (Double.doubleToLongBits(result) == Double.doubleToLongBits(expectedResult)) {
                        results.add(output.toString());
                    } else if (Double.doubleToLongBits(result) == Double.doubleToLongBits(-expectedResult)) {
                        output.insert(0, "-(").append(")");
                        results.add(output.toString());
                    }
                }
            }
        }
        return results;
    }

    private static void appendToOutput(List<String> operators, Double operand, int index, StringBuilder output) {
        if (index == 0) {
            for (int i = 0; i < operators.size() - 1; i++) {
                output.append("(");
            }
            output.append(operand.toString());
        } else {
            output.append(operators.get(index - 1)).append(operand.toString());
            if (index < operators.size()) {
                output.append(")");
            }
        }
    }

    private static Double calculate(Double operand1, Double operand2, String operator) {
        if ("+".equals(operator)) {
            operand1 += operand2;
        } else if ("-".equals(operator)) {
            operand1 -= operand2;
        } else if ("*".equals(operator)) {
            operand1 *= operand2;
        } else if ("/".equals(operator)) {
            operand1 /= operand2;
        }
        return operand1;
    }

}
